package com.nc.mem;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.Buffer;
import java.nio.ByteBuffer;

import sun.misc.Unsafe;

public final class PrivateAccessorImpl extends PrivateAccessor {

	static final Unsafe u;

	static final boolean unmapAvailable;
	static final boolean reallocAvailable;
	static final long addressOffset;
	static final long capacityOffset;
	static final long limitOffset;

	static {
		Unsafe unsafe = null;

		boolean rAvail = false;
		long aOff = 0;
		long cOff = 0;
		long lOff = 0;
		try {
			final Class<?> uType = Unsafe.class;
			final Field[] fields = uType.getDeclaredFields();

			for (final Field field : fields) {
				if (Modifier.isStatic(field.getModifiers()) && field.getType() == uType) {
					field.setAccessible(true);
					unsafe = (Unsafe) field.get(null);
					break;
				}
			}
			if (unsafe == null) {
				throw new ExceptionInInitializerError("Unable to load unsafe!");
			}

			try {
				aOff = unsafe.objectFieldOffset(Buffer.class.getDeclaredField("address"));
				cOff = unsafe.objectFieldOffset(Buffer.class.getDeclaredField("capacity"));
				lOff = unsafe.objectFieldOffset(Buffer.class.getDeclaredField("limit"));
				rAvail = true;
			} catch (final Throwable e) {
				System.err.println("Unable to load offsets. Reallocation will NOT be AVAILABLE: " + e.getMessage());
			}

			boolean canUnmap = false;
			try {
				Class.forName("sun.nio.ch.DirectBuffer");
				canUnmap = true;
			} catch (final Throwable e) {
				//
			}
			unmapAvailable = canUnmap;
		} catch (final Throwable err) {
			throw new ExceptionInInitializerError(err);
		}

		u = unsafe;
		reallocAvailable = rAvail;
		addressOffset = aOff;
		capacityOffset = cOff;
		limitOffset = lOff;
	}

	@Override
	public Object allocateInstance(final Class<?> clazz) throws InstantiationException {
		return u.allocateInstance(clazz);
	}

	@Override
	public boolean getBoolean(final Object ref, final long offset) {
		return u.getBoolean(ref, offset);
	}

	@Override
	public byte getByte(final Object ref, final long offset) {
		return u.getByte(ref, offset);
	}

	@Override
	public char getChar(final Object ref, final long offset) {
		return u.getChar(ref, offset);
	}

	@Override
	public double getDouble(final Object ref, final long offset) {
		return u.getDouble(ref, offset);
	}

	@Override
	public float getFloat(final Object ref, final long offset) {
		return u.getFloat(ref, offset);
	}

	@Override
	public int getInt(final Object ref, final long offset) {
		return u.getInt(ref, offset);
	}

	@Override
	public long getLong(final Object ref, final long offset) {
		return u.getLong(ref, offset);
	}

	@Override
	public Object getObject(final Object ref, final long offset) {
		return u.getObject(ref, offset);
	}

	@Override
	public short getShort(final Object ref, final long offset) {
		return u.getShort(ref, offset);
	}

	@Override
	public boolean isReallocationAvailable() {
		return reallocAvailable;
	}

	@Override
	public long objectFieldOffset(final Class<?> clazz, final String field) {
		try {
			return objectFieldOffset(clazz.getDeclaredField(field));
		} catch (final Throwable e) {
			u.throwException(e);
		}
		return -1L;
	}

	@Override
	public long objectFieldOffset(final Field field) {
		return u.objectFieldOffset(field);
	}

	@Override
	public void putBoolean(final Object ref, final long offset, final boolean val) {
		u.putBoolean(ref, offset, val);
	}

	@Override
	public void putByte(final Object ref, final long offset, final byte val) {
		u.putByte(ref, offset, val);
	}

	@Override
	public void putChar(final Object ref, final long offset, final char val) {
		u.putChar(ref, offset, val);
	}

	@Override
	public void putDouble(final Object ref, final long offset, final double val) {
		u.putDouble(ref, offset, val);
	}

	@Override
	public void putFloat(final Object ref, final long offset, final float val) {
		u.putFloat(ref, offset, val);
	}

	@Override
	public void putInt(final Object ref, final long offset, final int val) {
		u.putLong(ref, offset, val);
	}

	@Override
	public void putLong(final Object ref, final long offset, final long val) {
		u.putLong(ref, offset, val);
	}

	@Override
	public void putObject(final Object ref, final long offset, final Object val) {
		u.putObject(ref, offset, val);
	}

	@Override
	public void putShort(final Object ref, final long offset, final short val) {
		u.putShort(ref, offset, val);
	}

	@Override
	public void reallocateMemory(final ByteBuffer buffer, final int moreBytes, final boolean resizeLimit) {
		if (!isReallocationAvailable()) {
			throw new UnsupportedOperationException("Reallocation is not available!");
		}

		if (buffer == null || moreBytes <= 0) {
			throw new IllegalArgumentException("Buffer must not be null and moreBytes must be positive");
		}

		synchronized (buffer) {
			final int cap = buffer.capacity();
			final int newCap = cap + moreBytes;
			if (newCap < 0) {
				throw new UnsupportedOperationException("Attempting to reallocate for more than 2GB memory");
			}
			final long oldAddr = u.getLong(buffer, addressOffset);
			final long newAddr = u.reallocateMemory(oldAddr, newCap);

			u.putLongVolatile(buffer, addressOffset, newAddr);
			u.putIntVolatile(buffer, capacityOffset, newCap);
			if (resizeLimit) {
				u.putIntVolatile(buffer, limitOffset, newCap);
			}
		}

	}

	@Override
	public void unmap(final ByteBuffer buff) {
		if (unmapAvailable && buff != null && buff.isDirect() && buff instanceof sun.nio.ch.DirectBuffer) {
			((sun.nio.ch.DirectBuffer) buff).cleaner().clean();
		}
	}
}